<?php
class TemplateReferenceCompilerReturn {
	public $evaluated;
	public $value;	
}

class TemplateReferenceCompiler {

	/**
	 * Sets the value of a reference
	 *
	 * @param IGeryContext $context
	 * @param unknown_type $reference
	 * @param unknown_type $value
	 * @return unknown
	 */
	public static function setValue(IGeryContext $context, $reference, $value) {
		
		// if we haven't got any modifiers, just set the variable
		if (! count($reference['modifiers'])) 
		{
			$context->put($reference['name'], $value);
			return true;
		} 
		else 
		{
			// get the modifiers
			$modifiers = $reference['modifiers'];
			
			// pop the last modifier
			$lastModifier = array_pop($modifiers);
			
			// reassign the modifiers to the reference without the last modifier
			$reference['modifiers'] = $modifiers;
			
			// evalueate the reference
			$ret = self::evaluateReference($context, $reference);
			
			// if the reference could be evaluated
			if ($ret->evaluated) {
				
				$var = $ret->value;
				
				if (is_object($var)) {
					$methodName = 'set' . $lastModifier['name'];
					
					if (method_exists($var, $methodName)) {
						$var->$methodName($value);
						return true;
					}
				}
			}
		}
		
		// failed to set the variable
		return false;	
	}
	
	/**
	 * Evaluate a reference
	 *
	 * @param Context $context
	 * @param array $reference
	 * @return TemplateReferenceCompilerReturn
	 */
	public static function evaluateReference(IGeryContext $context, $reference) {	
		
		// get reference value
		$var = $context->get($reference['name']);
		
		// no value, so return false
		if ($var === null)
			return self::returnFalse();
		
		if ($var instanceof ReferenceProxy)	{
			$var = $var->invoke();
			
			if ($var === null)
				return self::returnFalse();
		}
		
		if (! count($reference['modifiers']))
			return self::returnTrue($var);
		
			
		return self::getValue($var, $reference['modifiers']);
	}
	
	public static function getValue($var, $modifiers) {
		
		foreach ($modifiers as $modifier) 
		{
			$name = $modifier['name'];
			$parameters = $modifier['parameters'];
			
			// array
			if (is_array($var) || $var instanceof IGeryArray) {
				
				if (! count($parameters) && array_key_exists($name, $var)) {
					$var = $var[$name];
					continue;
				}
				
				switch ($name) {
					case 'size':
						$var = count($var);
						break;
						
					case 'keys':
						$var = array_keys($var);
						break;
						
					case 'length':
						$var = count($var);
						break;
						
					case 'contains':
						$var = in_array($parameters[0], $var);
						break;
						
					case 'first':
						if (! count($var)) {
							return self::returnFalse();
						} else {
							$keys = array_keys($var);
							$var = $var[$keys[0]];
						}
						break;
						
					case 'last':
						if (! count($var)) {
							return self::returnFalse();
						} else {
							$var = end($var);
						}
						break;
						
					case 'search':
						$var = array_search($parameters[0], $var);
						break;
						
					case 'reverse':
						$var = array_reverse($var);
						break;
					
					default:
						return self::returnFalse();
				}
				
				continue;
				
			// string
			} else if (is_string($var)) {
				
				switch ($modifier['name']) {
					case 'length':
						$var = strlen($var);
						break;
						
					case 'upcase':
						$var = strtoupper($var);
						break;
						
					case 'downcase':
						$var = strtolower($var);
						break;
						
					case 'reverse':
						$var = strrev($var);
						break;
						
					case 'trim':
						$var = trim($var);
						break;
						
					case 'ucfirst':
						$var = ucfirst($var);
						break;
						
					case 'ucwords':
						$var = ucwords($var);
						break;
						
					case 'substring':
						if (count($parameters) == 1)
							$var = substr($var, (int)$parameters[0]);
						else if (count($parameters) == 2)
							$var = substr($var, (int)$parameters[0], (int)$parameters[1]);
						else
							return TemplateReferenceCompiler::returnFalse();
							
						break;
						
					default:
						return TemplateReferenceCompiler::returnFalse();
				}
				
				continue;
			
			// integers
			} else if (is_int($var)) {
				switch ($modifier['name']) {
					case 'odd':
						$t = $var;
						$var = ($t % 2) == 1;
						break;
						
					case 'months':
						$var = $var * 60 * 60 * 24 * 30;
						break;
						
					case 'years':
						$var = $var * 60 * 60 * 24 * 365;
						break;
						
					case 'weeks':
						$var *= 7;
						
					case 'days':
						$var *= 24;
						
					case 'hours':
						$var *= 60;
						
					case 'minutes':
						$var *= 60;
						break;
					
					case 'seconds';
						break;	
						 
					case 'ago':
						$var = date("Y-m-d H:i:s", time() - $var);
						break;
						
					case 'fromNow':
						$var = date("Y-m-d H:i:s", time() + $var);
						break;
						
					default:
						return TemplateReferenceCompiler::returnFalse();
				}
				
				continue;
				
			} else if (is_object($var)) {
				
				switch ($name) {
					case 'clone':
						$var = clone $var;
						break;
						
					default:
						$tryMethods = array(
							''
							,'get'
							,'is'
							,'has'
						);
						
						foreach ($tryMethods as $methodSuffix) {
							$methodName = $methodSuffix . ucfirst($name);
							
							if (method_exists($var, $methodName)) {
								$callback = array($var, $methodName);
								$var = call_user_func($callback, $parameters);
								break 2;
							}
						}
						
						return self::returnFalse();
				}
				
				continue;
			}
			
			// unknown modifier
			return TemplateReferenceCompiler::returnFalse();
		}
		
		return TemplateReferenceCompiler::returnTrue($var);
	}
	
	private static function returnFalse() {
		
		$return = new TemplateReferenceCompilerReturn();
		$return->evaluated = false;
		
		return $return;
	}
	
	private static function returnTrue($var) 
	{
		$return = new TemplateReferenceCompilerReturn();
		$return->evaluated = true;
		$return->value = $var;
		
		return $return;
	}
}
?>